package com.example.arouter_compiler;

import com.example.arouter_annatation.ARouter;
import com.example.arouter_annatation.ARouterBean;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import org.checkerframework.checker.units.qual.K;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

/**
 * 自定义注解处理器
 */

@AutoService(Processor.class)//通过AutoService用于注册
@SupportedAnnotationTypes("com.example.arouter_annatation.ARouter")//处理注解对象
@SupportedSourceVersion(SourceVersion.RELEASE_8)//jdk 版本
@SupportedOptions("content")//处理器接受参数（处理器为java项目，不能直接接受android项目的参数，通过该注解接受）
public class ArouterProcessor extends AbstractProcessor {
    //操作Elements工具类
    private Elements elementUtils;
    //类信息工具类
    private Types typeUtils;
    //用来输出日志
    private Messager messager;
    //文件生成器（生成类文件等）
    private Filer filer;

    /**
     * 初始化方法
     *
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
        String content = processingEnv.getOptions().get("content");
        messager.printMessage(Diagnostic.Kind.NOTE, content);


    }
    //生成类文件

    /**
     * 这个方法相当于main函数，开始处理注解，
     * 注解处理器的核心方法，例如可以在这里生成java文件
     *
     * @param sets     使用了支持处理注解的节点集合（例如类上面写了注解，方法上面写了注解等等）
     * @param roundEnv 运行环境，可以通过该对象查找到，找到注解。
     * @return true表示处理完成，false表示处理失败
     */
    Map<String, List<ARouterBean>> maps;

    @Override
    public boolean process(Set<? extends TypeElement> sets, RoundEnvironment roundEnv) {
        //获取项目中所有使用了com.example.arouter_annatation.ARouter注解的节点。
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ARouter.class);
        maps = new HashMap<>();
        for (Element element :
                elements) {
            //获取包节点(类节点之上的节点)
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            //获取类节点的名字(当前节点就是类节点，直接获取简单名字)
            String clazzName = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "packageName：" + packageName);
            messager.printMessage(Diagnostic.Kind.NOTE, "clazzName：" + clazzName);
            //获取注解的path值
            String path = element.getAnnotation(ARouter.class).path();
            //获取结点的group值
            String group = element.getAnnotation(ARouter.class).gourp();


            messager.printMessage(Diagnostic.Kind.NOTE, "path：" + path);
            messager.printMessage(Diagnostic.Kind.NOTE, "group：" + group);

            //生成的组的类名
            String groupClazzName = "ARouterGroup$" + group;
            //生成的path的类名
            String pathClazzName = "ARouterPath$" + group;

            messager.printMessage(Diagnostic.Kind.NOTE, "groupClazzName：" + groupClazzName);

            messager.printMessage(Diagnostic.Kind.NOTE, "pathClazzName：" + pathClazzName);

            ARouterBean bean = new ARouterBean.Builder().setPath(path).setGroup(group).setElement(element).builder();
            //将Activity 分组
            if (maps.containsKey(group)) {
                maps.get(group).add(bean);
            } else {
                List<ARouterBean> list = new ArrayList<>();
                list.add(bean);
                maps.put(group, list);
            }

        }

        //先生成path文件
        initPath(maps);
        TypeElement typeElement = elementUtils.getTypeElement("com.example.arouter_api.ARouterLoadPath");

        //然后生成group文件
        initGroup(maps, typeElement);


        return true;
    }

    /**
     * 生成Group文件
     *
     * @param maps
     */
    private void initGroup(Map<String, List<ARouterBean>> maps, TypeElement typeElement) {

        WildcardTypeName typeName = WildcardTypeName.subtypeOf(ClassName.get(typeElement));

        for (String key : maps.keySet()) {
            //key就是组

            //返回的类型，泛型key 泛型value
            TypeName mathodRerurn = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class),
                    ParameterizedTypeName.get(ClassName.get(Class.class), typeName));

            //方法
            MethodSpec.Builder methodSpecBuilder = MethodSpec
                    //方法名
                    .methodBuilder("loadGroup")
                    //修饰符
                    .addModifiers(Modifier.PUBLIC)
                    //注解
                    .addAnnotation(Override.class)
                    //返回值
                    .returns(mathodRerurn)

                    //方法体 $S字符串 $T类 $N 变量名
                    // Map<String, ARouterBean> maps = new HashMap<>();
                    // Map<String, Class<? extends ARouterLoadPath>> maps = new HashMap<>();
                    .addStatement("$T<$T, $T> $N = new $T<>()",
                            ClassName.get(Map.class),
                            ClassName.get(String.class),
                            ParameterizedTypeName.get(ClassName.get(Class.class), typeName),
                            "maps", ClassName.get(HashMap.class));


            //   maps.put("order", ARouter$$Path$$Order.class);
            String packageName = elementUtils.getPackageOf(maps.get(key).get(0).getElement()).getQualifiedName().toString();
            methodSpecBuilder.addStatement("$N.put(\"" + key + "\", $T.class);"
                    , "maps", ClassName.get(packageName, "ARouterPath" + key));


            methodSpecBuilder.addStatement("return $N", "maps")
                    .build();





            //

            TypeSpec interfaceSpec = TypeSpec.interfaceBuilder("ARouterLoadGroup")
                    .build();

            TypeName interfaceTypeName = ClassName.get("com.example.arouter_api", interfaceSpec.name);


            //类名
            TypeSpec typeSpec = TypeSpec.classBuilder("ARouterGroup" + key)
                    //类 修饰符
                    .addModifiers(Modifier.PUBLIC)
                    //添加实现接口
                    .addSuperinterface(interfaceTypeName)
                    //添加方法
                    .addMethod(methodSpecBuilder.build())

                    .build();
            //引入包
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .build();

            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }



        }

    }

    /**
     * 生成path文件
     * @param maps
     */
    private void initPath(Map<String, List<ARouterBean>> maps) {

        for (String key : maps.keySet()) {
            List<ARouterBean> list = maps.get(key);

            //返回的类型，泛型key 泛型value
            // TypeName mathodRerurn = ParameterizedTypeName.get(Map.class, String.class, ARouterBean.class);
            TypeName mathodRerurn = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(ARouterBean.class));
            //方法
            MethodSpec.Builder methodSpecBuilder = MethodSpec
                    //方法名
                    .methodBuilder("loadPath")
                    //修饰符
                    .addModifiers(Modifier.PUBLIC)

                    //注解
                    .addAnnotation(Override.class)
                    //返回值
                    .returns(mathodRerurn)

                    //方法体 $S字符串 $T类 $N 变量名
                    // Map<String, ARouterBean> maps = new HashMap<>();
                    .addStatement("$T<$T, $T> $N = new $T<>()",
                            ClassName.get(Map.class),
                            ClassName.get(String.class),
                            ClassName.get(ARouterBean.class),
                            "maps", ClassName.get(HashMap.class));


            for (ARouterBean bean : list) {


                // maps.put("user/UserActivity", ARouterBean.create(ARouterBean.Type.ACTIVITY, UserActivity.class, "user/UserActivity", "user"));

                methodSpecBuilder.addStatement("$N.put(\"" + bean.getPath() + "\", $T.create($T.Type.ACTIVITY, $T.class, \"" + bean.getPath() + "\", \"" + bean.getGroup() + "\"))"

                        , "maps", ClassName.get(ARouterBean.class), ClassName.get(ARouterBean.class), ClassName.get(elementUtils.getPackageOf(bean.getElement()).getQualifiedName().toString(), bean.getPath().split("/")[1]));


            }

            methodSpecBuilder.addStatement("return $N", "maps")
                    .build();


            TypeSpec interfaceSpec = TypeSpec.interfaceBuilder("ARouterLoadPath")
                    .build();

            TypeName interfaceTypeName = ClassName.get("com.example.arouter_api", interfaceSpec.name);


            //类名
            TypeSpec typeSpec = TypeSpec.classBuilder("ARouterPath" + key)
                    //类 修饰符
                    .addModifiers(Modifier.PUBLIC)
                    //添加实现接口
                    .addSuperinterface(interfaceTypeName)
                    //添加方法
                    .addMethod(methodSpecBuilder.build())

                    .build();
            //引入包
            JavaFile javaFile = JavaFile.builder(elementUtils.getPackageOf(list.get(0).getElement()).getQualifiedName().toString(), typeSpec)
                    .build();

            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }


        }


    }


}
